grammar WL;

options {language = CSharp3;}

@lexer::namespace {WLCompiler.Helpers.ANTLR}

@parser::namespace {WLCompiler.Helpers.ANTLR}

@header
{
	using System;
	using System.Collections;
	
	using WLCompiler.Helpers;
	using WLCompiler.Helpers.SemanticAnalyze;
	using WLCompiler.Helpers.Extensions;
	using WLCompiler.Helpers.ListOperations;
	
	using System.Reflection;
	using System.Reflection.Emit;
}

@members
{
	AssemblyCreator creator = new AssemblyCreator(); 	
}

  public
  compileUnit
  @after
  {
  	creator.SaveAssembly();
  }
	 : declaration+ EOF
	 ;

  declaration
	 : variable[ScopeType.Global]
	 {
	 	creator.DeclareGlobalVariable($variable.Instance, $variable.Line);
	 }
	 | function
	 ;

  variable[ScopeType scopeType] returns [Variable Instance, int Line]
	 : type ID SEMI
	 {
	 	$Instance = new Variable(scopeType, $ID.text, $type.Instance);
	 	$Line = $ID.line;
	 }
	 ;
  
  type returns [Type Instance]
  	:  INT_T {$Instance = typeof(int);}
  	| LIST_T {$Instance = typeof(List<int>);}
  	| VOID_T {$Instance = typeof(void);}
  	;
  	
  function
  scope
  {
  	Function fun;
  	List<Variable> args;
  }
  
  @init
  {
  	$function::args = new List<Variable>();
  }
  
 	: type ID L_PAREN ( first = funcparam[ScopeType.Arg]{$function::args.Add($first.Instance);} (COMMA other = funcparam[ScopeType.Arg]{$function::args.Add($other.Instance);})*)? R_PAREN
  	{
  		$function::fun = creator.DefineGlobalFunction(new Function($ID.text, $type.Instance, $function::args), $ID.line);
  	}
  	 L_BRACE body[$function::fun, new ReferenceContainer<Variable>()] R_BRACE
  	;
  	
  funcparam[ScopeType scopeType] returns [Variable Instance]
  	: type ID
  	{
  		$Instance = new Variable(scopeType, $ID.text, $type.Instance);
  	}
  	;
  	
  body[Function Function, ReferenceContainer<Variable> UpperLocals]
  scope
  {
  	Function fun;
  	ReferenceContainer<Variable> locals;
  	ReferenceContainer<Variable> upLocals;
  }
  
  @init
  {
  	$body::fun = $body.Function;
  	$body::locals = new ReferenceContainer<Variable>();
  	$body::upLocals = $body.UpperLocals;
  }
  	: statement+
  	;
  	
  statement
	: if_st
	| for_loop
	| while_loop
	| until_loop
	| variable[ScopeType.Local]
	{
		var local = creator.DeclareLocalVariable($variable.Instance, $body::locals, $body::fun, $variable.Line);
		$body::locals.Add(local);
	}
	| write SEMI
	| read SEMI
	| callfunc SEMI
	| assignexp SEMI
  	| return SEMI
  	| ID
  	{
  		var ilGen = $body::fun.FunctionReference.GetILGenerator();
		var variable = creator.Table.ResolveVariable($ID.text, $body::locals, $body::fun, $body::upLocals);
		
		if (variable != null)
		{
			if (variable.Type == typeof(List<int>))
			{
				if (variable.ScopeType == ScopeType.Local)
				{
					ilGen.Emit(OpCodes.Ldloc_S, (LocalBuilder)variable.VariableReference);
				}
				
				if (variable.ScopeType == ScopeType.Global)
				{
					ilGen.Emit(OpCodes.Ldsfld, (FieldBuilder)variable.VariableReference);
				}
				
				if (variable.ScopeType == ScopeType.Arg)
				{
					ilGen.Emit(OpCodes.Ldarg, ((ParameterBuilder)variable.VariableReference).Position);
				}
			}
			else
			{
				PrintHelper.PrintError($ID.line, "List operations only allowed with 'list' type.");
			}
		}
		else
		{
			PrintHelper.PrintError($ID.line, "List '" + $ID.text + "' is not defined.");
		}	
  	}
  	 liststatements expression SEMI
  	{
  		//var ilGen = $body::fun.FunctionReference.GetILGenerator();
		
		if ($expression.Type == typeof(int))
		{
			ilGen.Emit(OpCodes.Callvirt, $liststatements.Operation);
		}
		else
		{
			PrintHelper.PrintError($ID.line, "Cannot implicitly convert '" + $expression.Type.ToString() + "' to '"
			 + typeof(int).ToString() + "'.");
		}
  	}
	;
	
  write
  	: PRINT expression
  	{
  		var ilGen = $body::fun.FunctionReference.GetILGenerator();
  		
  		if ($expression.Type == typeof(List<int>))
  		{
  			ilGen.Emit(OpCodes.Call, typeof(ListOperations.ListOperations).GetMethod("PrintList", new [] {typeof(List<int>)}));	
  		}
  		else
  		{
	  		ilGen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new [] {$expression.Type}));
  		}	
  	}
  	;
  	
  read
  	: READ ID
  	{
  		var ilGen = $body::fun.FunctionReference.GetILGenerator();
		var l = $body::locals;
		var ul = $body::upLocals;
		var variable = creator.Table.ResolveVariable($ID.text, l, $body::fun, ul);
		
		if (variable == null)
		{
			PrintHelper.PrintError($ID.line, "Variable '" + $ID.text + "' is not defined.");
		}
		else
		{	
			if (variable.Type == typeof(List<int>))
			{
				ilGen.Emit(OpCodes.Call, typeof(ListOperations.ListOperations).GetMethod("ReadList", Type.EmptyTypes));	
			}
		
			if (variable.Type == typeof(int))
			{
				ilGen.Emit(OpCodes.Call, typeof(Console).GetMethod("ReadLine", Type.EmptyTypes));
				ilGen.Emit(OpCodes.Call, typeof(int).GetMethod("Parse", new [] {typeof(string)}));	
			}
			
			if (variable.ScopeType == ScopeType.Local)
			{
				ilGen.Emit(OpCodes.Stloc_S, (LocalBuilder)variable.VariableReference);
			}
	
			if (variable.ScopeType == ScopeType.Global)
			{
				ilGen.Emit(OpCodes.Stsfld, (FieldBuilder)variable.VariableReference);
			}
			
			if (variable.ScopeType == ScopeType.Arg)
			{
				//ilGen.Emit(OpCodes.Ldarg, ((ParameterBuilder)variable.VariableReference).Position);
				PrintHelper.PrintError($ID.line, "Cannot read arg.: '" + $ID.text + "' from the console.");
			}
		}	
  	}
  	;
	
READ
	: 'read'
	;
	
  assignexp
  	: ID ASSIGN expression
  	{	
  		var ilGen = $body::fun.FunctionReference.GetILGenerator();
		var variable = creator.Table.ResolveVariable($ID.text, $body::locals, $body::fun, $body::upLocals);
		
		if (variable != null)
		{
			if (variable.Type != $expression.Type)
			{
				PrintHelper.PrintError($ID.line, "Cannot implicitly convert '" + variable.Type.ToString() + "' to '" + $expression.Type.ToString() + "'.");
			}
			else
			{	
				if (variable.ScopeType == ScopeType.Local)
				{
					ilGen.Emit(OpCodes.Stloc_S, (LocalBuilder)variable.VariableReference);
				}
		
				if (variable.ScopeType == ScopeType.Global)
				{
					ilGen.Emit(OpCodes.Stsfld, (FieldBuilder)variable.VariableReference);
				}
				
				if (variable.ScopeType == ScopeType.Arg)
				{
					//ilGen.Emit(OpCodes.Ldarg, ((ParameterBuilder)variable.VariableReference).Position);
					PrintHelper.PrintError($ID.line, "Cannot assign arg. '" + $ID.text + "' to" + "'" + $expression.Type.ToString() + "'.");
				}
			}
		}
	}
  	;
	
  if_st
  scope
  {
  	Function fun;
  	ReferenceContainer<Variable> upLocals;
  }
  
  @init
  {
  	$if_st::fun = $body::fun;
  	$if_st::upLocals = $body::locals + $body::upLocals;
  }
	: IF L_PAREN compareexp[null] R_PAREN L_BRACE body[$if_st::fun, $if_st::upLocals] R_BRACE
	{
		var ilGen = $if_st::fun.FunctionReference.GetILGenerator();
		ilGen.MarkLabel($compareexp.Label);
	}
	;
	
  for_loop
  scope
  {
  	Function fun;
  	ReferenceContainer<Variable> upLocals;
  }
  
  @init
  {
  	$for_loop::fun = $body::fun;
  	$for_loop::upLocals = $body::locals+ $body::upLocals;
  }
  	: FOR L_PAREN assignexp SEMI
  	{
  		var ilGen = $for_loop::fun.FunctionReference.GetILGenerator();
	  	var repeat = ilGen.DefineLabel();
	  	ilGen.MarkLabel(repeat);
  	}
  	 compareexp[null] SEMI
  	{
  		var skipAssign = ilGen.DefineLabel();
  		ilGen.Emit(OpCodes.Br_S, skipAssign);
  		var beforeAssign = ilGen.DefineLabel();
  		ilGen.MarkLabel(beforeAssign);	
  	}
  	 assign = assignexp{ilGen.Emit(OpCodes.Br_S, repeat); ilGen.MarkLabel(skipAssign);} R_PAREN L_BRACE body[$for_loop::fun, $for_loop::upLocals] R_BRACE
  	{
  		ilGen.Emit(OpCodes.Br_S, beforeAssign);
		ilGen.MarkLabel($compareexp.Label);
  	}
  	;
  	
  while_loop
  scope
  {
  	Function fun;
  	ReferenceContainer<Variable> upLocals;
  }
  
  @init
  {
  	$while_loop::fun = $body::fun;
  	$while_loop::upLocals = $body::locals+ $body::upLocals;
  }
  	: WHILE
  	{
  		var ilGen = $while_loop::fun.FunctionReference.GetILGenerator();
	  	var repeat = ilGen.DefineLabel();
	  	ilGen.MarkLabel(repeat);
  	}
  	 L_PAREN compareexp[null] R_PAREN  L_BRACE body[$while_loop::fun, $while_loop::upLocals] R_BRACE
  	{
  		ilGen.Emit(OpCodes.Br_S, repeat);
		ilGen.MarkLabel($compareexp.Label);
  	}
	;
	
until_loop
scope
  {
  	Function fun;
  	ReferenceContainer<Variable> upLocals;
  	Label repeat;
  }
  
  @init
  {
  	$until_loop::fun = $body::fun;
  	$until_loop::upLocals = $body::locals+ $body::upLocals;
  }
	: DO
	{
		var ilGen = $until_loop::fun.FunctionReference.GetILGenerator();
		$until_loop::repeat = ilGen.DefineLabel();
		ilGen.MarkLabel($until_loop::repeat);
	}
	 L_BRACE body[$until_loop::fun, $until_loop::upLocals] R_BRACE UNTIL L_PAREN compareexp[$until_loop::repeat] R_PAREN
	;

  compareexp[Label? Predef] returns [Label Label]
  	: one = expression compare two = expression
	{
		if ($one.Type == $two.Type)
		{
			var ilGen = $body::fun.FunctionReference.GetILGenerator();
			
			if ($compareexp.Predef != null)
			{
				ilGen.Emit($compare.Operation, $compareexp.Predef.Value);
				$Label = $compareexp.Predef.Value;
			}
			else
			{
				var label = ilGen.DefineLabel();
				ilGen.Emit($compare.Operation, label);
				$Label = label;
			}
		}
		else
		{
			PrintHelper.PrintError($compare.Line, "Cannot implicitly convet '" + $one.Type + "' to '" + $two.Type + "'.");
		}
	}	
  	;
  	
  compare returns [OpCode Operation, int Line]
  	: EQUALS {$Operation = OpCodes.Bne_Un_S; $Line = $EQUALS.line;}
  	| LESS {$Operation = OpCodes.Bge_S; $Line = $LESS.line;}
  	| MORE {$Operation = OpCodes.Ble_S; $Line = $MORE.line;}
  	;
  	
  expression returns [Type Type]
  scope
  {
  	OpCode action;
  	int line;
  }
  	: one = multiplicativeexp ( (PLUS{$expression::action = OpCodes.Add; $expression::line = $PLUS.line;} 
  			| MINUS{$expression::action = OpCodes.Sub; $expression::line = $MINUS.line;}) two = multiplicativeexp )*
  	{
  		if ($expression::action != default(OpCode))
  		{
  			if ($one.Type != $two.Type)
  			{
  				PrintHelper.PrintError($expression::line, "Cannot implicitly convert '" + $one.Type.ToString() + "' to '" + $two.Type.ToString() + "'.");
  			}
  			else
  			{
  				var ilGen = $body::fun.FunctionReference.GetILGenerator();
				ilGen.Emit($expression::action);
  			}
  		}
  		
		$Type = $one.Type;
  	}
  	| listexp
  	{
  		$Type = $listexp.Type;
  	}
  	| callfunc
  	{
  		$Type = $callfunc.Type;
  	}
  	;
  	
  listexp returns [Type Type]
  	: ID
  	{
		var ilGen = $body::fun.FunctionReference.GetILGenerator();
		var l = $body::locals;
		var ul = $body::upLocals;
		var variable = creator.Table.ResolveVariable($ID.text, l, $body::fun, ul);
		
		if (variable == null)
		{
			PrintHelper.PrintError($ID.line, "Variable '" + $ID.text + "' is not defined.");
		}
		else
		{
			if (variable.Type != typeof(List<int>))
			{
				PrintHelper.PrintError($ID.line, "Variable '" + $ID.text + "' should be typed as '" + typeof(List<int>) + "'.");
			}
			else
			{
				if (variable.ScopeType == ScopeType.Local)
				{
					ilGen.Emit(OpCodes.Ldloc_S, (LocalBuilder)variable.VariableReference);
				}
				
				if (variable.ScopeType == ScopeType.Global)
				{
					ilGen.Emit(OpCodes.Ldsfld, (FieldBuilder)variable.VariableReference);
				}
				
				if (variable.ScopeType == ScopeType.Arg)
				{
					ilGen.Emit(OpCodes.Ldarg, ((ParameterBuilder)variable.VariableReference).Position);
				}
			}
		}
	}
  	 listexpbody
  	 {
  	 	$Type = $listexpbody.Type;
  	 }
  	;
  	
  listexpbody returns [Type Type]
  	: INDEX_OF expression
  	 {
  	 	var ilGen = $body::fun.FunctionReference.GetILGenerator();
  	 
  		if ($expression.Type != typeof(int))
  		{
  			PrintHelper.PrintError($INDEX_OF.line, "Expression at the right side of ':' should be 'int' typed.");
  		}
  		else
  		{
  			ilGen.Emit(OpCodes.Call, typeof(ListOperations.ListOperations).GetMethod("IndexOf", new [] {typeof(List<int>), typeof(int)}));
  		}	 
  	 
  	 	$Type = typeof(int);	
  	 }
  	| L_BRACK expression R_BRACK
  	{
  		var ilGen = $body::fun.FunctionReference.GetILGenerator();
  	
  		if ($expression.Type != typeof(int))
  		{
  			PrintHelper.PrintError($L_BRACK.line, "Expression within the '[..]' should be 'int' typed.");
  		}
  		else
  		{
  			ilGen.Emit(OpCodes.Call, typeof(ListOperations.ListOperations).GetMethod("ElementAt", new [] {typeof(List<int>), typeof(int)}));
  		}	 
  	 
  	 	$Type = typeof(int);	
  	 }
  	 | COUNT
  	 {
  	 	var ilGen = $body::fun.FunctionReference.GetILGenerator();
  	 	ilGen.Emit(OpCodes.Call, typeof(ListOperations.ListOperations).GetMethod("Count", new [] {typeof(List<int>)}));
  	 	
  	 	$Type = typeof(int);
  	 }
  	 | CONTAINS expression
  	 {
  	 	var ilGen = $body::fun.FunctionReference.GetILGenerator();
  	
  		if ($expression.Type != typeof(int))
  		{
  			PrintHelper.PrintError($CONTAINS.line, "Expression at the right side of '?' should be 'int' typed.");
  		}
  		else
  		{
  			ilGen.Emit(OpCodes.Call, typeof(ListOperations.ListOperations).GetMethod("Contains", new [] {typeof(List<int>), typeof(int)}));
  		}	 
  	 
  	 	$Type = typeof(int);
  	 }
	| SUM_LIST
	{
		var ilGen = $body::fun.FunctionReference.GetILGenerator();
  	 	ilGen.Emit(OpCodes.Call, typeof(ListOperations.ListOperations).GetMethod("Sum", new [] {typeof(List<int>)}));
	
		$Type = typeof(int);
	}
	| MULT_LIST	
	{
		var ilGen = $body::fun.FunctionReference.GetILGenerator();
  	 	ilGen.Emit(OpCodes.Call, typeof(ListOperations.ListOperations).GetMethod("Mult", new [] {typeof(List<int>)}));
	
		$Type = typeof(int);
	}
  	;
  	
  SUM_LIST
  	: '++'
  	;
  	
  MULT_LIST
  	: '**'
  	;
  	
  CONTAINS
  	: '?'
  	;
  	
  COUNT
  	: '~'
  	;
  	
  INDEX_OF
  	: ':'
  	;
  	
  L_BRACK
  	: '['
  	;
  	
  R_BRACK
  	: ']'
  	;
  	
  liststatements returns [MethodInfo Operation, int Line]
  	: ADD_OP
  	{
  		$Operation = typeof(List<int>).GetMethod("Add", new [] {typeof(int)});
  		$Line = $ADD_OP.line;
  	}
  	| REMOVE_OP
  	{
  		$Operation = typeof(List<int>).GetMethod("Remove", new [] {typeof(int)});
  		$Line = $REMOVE_OP.line;
  	}
  	;
  	
  multiplicativeexp returns [Type Type]
  scope
  {
	OpCode action;
	int line;
  }
  	: one = additiveexp ( (MULT{$multiplicativeexp::action = OpCodes.Mul; $multiplicativeexp::line = $MULT.line;}
  			 | DIV{$multiplicativeexp::action = OpCodes.Div; $multiplicativeexp::line = $DIV.line;}) two = additiveexp )*
  	{	
  		if ($multiplicativeexp::action != default(OpCode))
  		{
  			if ($one.Type != $two.Type)
  			{
  				PrintHelper.PrintError($multiplicativeexp::line, "Cannot convert '" + $one.Type.ToString() + "' to '" + two ?? $two.Type.ToString() + "'.");
  			}
  			else
  			{
  				var ilGen = $body::fun.FunctionReference.GetILGenerator();
				ilGen.Emit($multiplicativeexp::action);
  			}		
  		}
  		
		$Type = $one.Type;
  	}
  	;
  	
  additiveexp returns [Type Type]
	: atom
	{
		$Type = $atom.Type;
	}
	
	;
	
  atom returns [Type Type]
	: INT
	{
		var ilGen = $body::fun.FunctionReference.GetILGenerator();
		ilGen.Emit(OpCodes.Ldc_I4_S, int.Parse($INT.text));
		
		$Type = typeof(int);
	}
	| ID
	{
		var ilGen = $body::fun.FunctionReference.GetILGenerator();
		var l = $body::locals;
		var ul = $body::upLocals;
		var variable = creator.Table.ResolveVariable($ID.text, l, $body::fun, ul);
		
		if (variable == null)
		{
			PrintHelper.PrintError($ID.line, "Variable '" + $ID.text + "' is not defined.");
		}
		else
		{
			if (variable.ScopeType == ScopeType.Local)
			{
				ilGen.Emit(OpCodes.Ldloc_S, (LocalBuilder)variable.VariableReference);
			}
			
			if (variable.ScopeType == ScopeType.Global)
			{
				ilGen.Emit(OpCodes.Ldsfld, (FieldBuilder)variable.VariableReference);
			}
			
			if (variable.ScopeType == ScopeType.Arg)
			{
				ilGen.Emit(OpCodes.Ldarg, ((ParameterBuilder)variable.VariableReference).Position);
			}
			
			$Type = variable.Type;
		}
	}
	| STRING
	{
		var ilGen = $body::fun.FunctionReference.GetILGenerator();
		var str = $STRING.text;
		str = str.Substring(1, str.Length - 2);
		ilGen.Emit(OpCodes.Ldstr, str);
		
		$Type = typeof(string);
	}
	| L_PAREN expression R_PAREN
	{
		$Type = $expression.Type;
	}
	;
  	
  callfunc returns [Type Type]
  scope
  {
  	List<Type> argTypes;
  }
  
  @init
  {
  	$callfunc::argTypes = new List<Type>();
  }
  	: ID L_PAREN ( one = expression{$callfunc::argTypes.Add($one.Type);} (COMMA two = expression{$callfunc::argTypes.Add($two.Type);})* )? R_PAREN
  	{
  		var ilGen = $body::fun.FunctionReference.GetILGenerator();
		var function = creator.Table.ResolveFunction($ID.text);
		
		if (function != null)
				{
			ilGen.Emit(OpCodes.Call, function.FunctionReference);
			
			$Type = function.Type;
		}
		else
		{
			PrintHelper.PrintError($ID.line, "Function '" + $ID.text + "' is not defined.");
		}
  	}
  	;
  	
  return
  	: RETURN expression?
  	 {
  		var ilGen = $body::fun.FunctionReference.GetILGenerator();
  		ilGen.Emit(OpCodes.Ret);
  	}
	;
	
PRINT
	: 'print'
	;
	
	COMMA
	: ','
	;

PLUS
	: '+'
	;
	
MINUS
	: '-'
	;
	
DIV
	: '/'
	;
	
MULT
	: '*'
	;
			
RETURN
	: 'return'
	;
	
IF
	: 'if'
	;
	
FOR	
	: 'for'
	;
	
WHILE
	:  'while'
	;
	
UNTIL
	: 'until'
	;
	
DO
	: 'do'
	;
	
EQUALS
	: '=='
	;
	
LESS
	: '<'
	;
	
MORE
	: '>'
	;
  	
 L_PAREN 
  	: '('
  	;
  	
R_PAREN
  	: ')'
  	;
  	
 L_BRACE
	: '{'
	;

R_BRACE
	: '}'
	;
  	
LIST_T
	: 'list'
	;	

INT_T
	: 'int'
	;
	
VOID_T
	: 'void'
	;

ASSIGN
	: '='
	;

SEMI
	: ';'
	;

 ADD_OP
 	: '<<'
 	;
 	
 REMOVE_OP
 	: '>>'
 	;

ID
  	: ( 'a'..'z' | 'A'..'Z' | '_' ) ( 'a'..'z' | 'A'..'Z' | '0'..'9' | '_' )*;

INT
	  :
	  '0'..'9'+
	  ;

STRING
	  : '"' ( 'a'..'z' | 'A'..'Z' | '0'..'9' | ' ' )*  '"'
	  ;

WS
	  : ( ' ' | '\t' | '\r' | '\n' )
	   {$channel = Hidden;}
	  ;
